-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix setState regression #1771
Fix setState regression #1771
Conversation
I have noticed the following regression after upgrade to enzyme 3.4.4. > TypeError: ReactWrapper::setState() expects a function as its second argument I think that we are closer to how React behaves this way: https://github.com/facebook/react/blob/5031ebf6beddf88cac15f4d2c9e91f8dbb91d59d/packages/react-reconciler/src/ReactFiberReconciler.js#L144-L153.
Well, my fix is probably wrong, I don't have the time to look deeper, but at least you get the idea. This is fine with React, I don't think that enzyme should throw. this.setState({}, null); // OK
this.setState({}, undefined); // OK
this.setState({}, () => {}); // OK
this.setState({}, {}); // NOT OK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why shouldn't it throw? If you're doing that in React, it's probably a bug. That React chooses not to throw is fine, but enzyme is a test framework - its job is to catch bugs.
Do you have a legitimate use case for passing a falsy non-function to either setState or setProps?
if (this[ROOT] !== this) { | ||
throw new Error('ShallowWrapper::setProps() can only be called on the root'); | ||
} | ||
if (typeof callback !== 'function') { | ||
if (callback && typeof callback !== 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you pass undefined
, the current code will replace it with noop
- this would allow you to silently pass a falsy primitive (false, NaN, 0, empty string, null). What's the utility of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about null
? The default won't kick in. React checks both:
https://github.com/facebook/react/blob/5031ebf6beddf88cac15f4d2c9e91f8dbb91d59d/packages/react-reconciler/src/ReactFiberClassComponent.js#L179
function b(b = 'b') {
return b
}
b() // 'b'
b(null) // null
b(undefined) // 'b'
b(false) // false
But I haven't poor much time in this patch, I could be wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that null
won't trigger the default; but i'm confused why you'd ever pass in a null in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm confused why you'd ever pass in a null in the first place.
Good question, I have no idea. I was curious to see how React would behave, turns out, they explicitly support it for some reason I'm not aware of.
https://github.com/facebook/react/blob/5031ebf6beddf88cac15f4d2c9e91f8dbb91d59d/packages/react-reconciler/src/ReactFiberClassComponent.js#L179
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've asked for the motivation here
@@ -399,11 +399,11 @@ class ShallowWrapper { | |||
* @param {Function} cb - callback function | |||
* @returns {ShallowWrapper} | |||
*/ | |||
setProps(props, callback = noop) { | |||
setProps(props, callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
either way, the default is required, so the .length
of the function is correct.
How is it a bug if it's working in production?
I believe I have, the callback parameter is optional in my case, it can be |
If you pass |
It's a bug because it's unintentional - that something happens to work doesn't make it not a bug. If you're passing "not a callback", and "not nothing", where a callback is expected, you made a mistake, plain and simple. |
If you pass |
Ah, you're right, I added that to setState (but not setProps). That stricter check, however, has already caught a bug in someone's code - see #1769. I think that in this case the benefits outweigh the deviation from React in production. (Separately, I'm going to try to pursue adding the same strict check in react itself in v17) |
I do see, however, that because of recent changes, the wrapper's |
@ljharb My main purpose with this pull request was to illustrate the change of behavior. I have already patched the Material-UI code to always have a callback function defined. Personally, I would stick to React behavior as much as possible. But I can understand you have a different vision. Do what you think is best. |
Per the discussion; I'm going to address this by leaving the wrapper methods strict, but by making the shallow |
@ljharb I haven't checked the implementation, but this behavior description sounds great, thanks :)! |
I have noticed the following regression after upgrade to enzyme 3.4.4.
I think that we are closer to how React behaves this way: https://github.com/facebook/react/blob/5031ebf6beddf88cac15f4d2c9e91f8dbb91d59d/packages/react-reconciler/src/ReactFiberClassComponent.js#L77-L92